import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.text.DecimalFormat;
import javax.swing.BorderFactory;
import javax.swing.JApplet;
import javax.swing.JDialog;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;

/**
 * 
 * JPanel that asks the user to Subitize
 *
 */
public class SubitizePanel extends JPanel implements ActionListener, KeyListener
{
	
	private Circle[] circles;
	private JButton start;
	private JButton advanced;
	private JButton backToSettings;
	private JDialog dialog;
	private int maxNumberOfCircles;
	private int minNumberOfCircles;
	private int numberOfCircles;
	public static final int DEFAULT_CIRCLE_SIZE= 50;
	private int circleSize;
	private static final int GAP= 20;
	private int questionCount;
	private int maxQuestionCount= 20;
	private int correct;
	private int incorrect;
	private int correctOnFirstTry;
	private int incorrectOnFirstTry;
	private int timesPlayed;
	private int firstKey;
	private boolean begun;
	private boolean onFirstTry;
	private boolean displayResults;
	private boolean waitingOnSecondKey;
	private long startTime;
	private long elapsedTime;
	private long questionStartTime;
	private long questionElaspedTime;
	private DynamicLongArray sessionTimes;
	private int[] correctAtNumber;
	private int[] incorrectAtNumber;
	private long[] averageTimeAtNumber;
	private DecimalFormat percentageFormat;
	private DecimalFormat timeFormat;
	private Font font;
	private Color color;
	
	/**
	 * 
	 * @param maxNumberOfCircles the maximum number of circles to display
	 * @param minNumberOfCircles the minimum number of circles to display
	 * @param color the color to draw the circles
	 * @param background the background color
	 */
	public SubitizePanel(int maxNumberOfCircles, int minNumberOfCircles, int circleSize, 
						 Color color, Color background)
	{
		this.circleSize= circleSize;
		this.maxNumberOfCircles= maxNumberOfCircles;
		this.minNumberOfCircles= minNumberOfCircles;
		this.color= color;
		setPreferredSize(computeSize());
		addKeyListener(this);
		setBorder(BorderFactory.createLineBorder(Color.BLUE));
		setBackground(background);
		start= new JButton("Start");
		start.setMnemonic(KeyEvent.VK_S);
		advanced= new JButton("Stats");
		advanced.setMnemonic(KeyEvent.VK_A);
		backToSettings= new JButton("Back To Settings");
		backToSettings.setMnemonic(KeyEvent.VK_B);
		add(start);
		add(advanced);
		add(backToSettings);
		advanced.setVisible(false);
		start.addActionListener(this);
		advanced.addActionListener(this);
		backToSettings.addActionListener(this);
		dialog= new JDialog(new Frame(), "Stats");
		StatsPane statsPane= new StatsPane(Global.currentUser);
		dialog.setContentPane(statsPane);
		dialog.setSize(new Dimension(500, 500));
		dialog.setDefaultCloseOperation(JDialog.HIDE_ON_CLOSE);
		dialog.setVisible(false);
		percentageFormat= Global.PERCENTAGE_FORMAT;
		timeFormat= Global.TIME_FORMAT;
		font= new Font("Serif", Font.PLAIN, 16);
		begun= false;
		correct= 0;
		incorrect= 0;
		correctOnFirstTry= 0;
		incorrectOnFirstTry= 0;
		onFirstTry= true;
		questionCount= 0;
		timesPlayed= 0;
		displayResults= false;
		waitingOnSecondKey= false;
		sessionTimes= new DynamicLongArray();
		correctAtNumber= new int[maxNumberOfCircles];
		incorrectAtNumber= new int[maxNumberOfCircles];
		averageTimeAtNumber= new long[maxNumberOfCircles];
	}
	
	/**
	 * handles button presses
	 */
	@Override
	public void actionPerformed(ActionEvent e) 
	{
		if(e.getActionCommand()==start.getActionCommand())
		{
			requestFocusInWindow();
			start.setVisible(false);
			advanced.setVisible(false);
			backToSettings.setVisible(false);
			displayResults= false;
			randomizeCircles();
			begun= true;
			reset();
		}
		else if(e.getActionCommand()==advanced.getActionCommand())
		{
			StatsPane statsPane= new StatsPane(Global.currentUser);
			dialog.setContentPane(statsPane);
			dialog.setLocation(getX(), getY());
			dialog.setVisible(true);
		}
		else
		{
			dialog.dispose();
			setVisible(false);
			Component ancestor= this.getTopLevelAncestor();
			if(Global.isApplet()) 
			{
				JApplet applet= (JApplet)(ancestor);
				applet.setContentPane(new SettingsPanel());
			}
			else 
			{
				JFrame frame= (JFrame)(ancestor);
				frame.setContentPane(new SettingsPanel());
				frame.pack();
			}
		}
	}
	
	/**
	 * Determines the dimensions that the window should take may decide 
	 * to override the user specified size for the circles.
	 *
	 * @return the Dimension the window should use.
	 */
	private Dimension computeSize()
	{
		final int DEFAULT_SIZE= 450;
		while(true)
		{
			Dimension screenSize= Toolkit.getDefaultToolkit().getScreenSize();
			int height= screenSize.height;
			int width= screenSize.width;
			int limit;
			if(height<width) limit= height;
			else limit= width;
			int suggestedSize= circleSize*maxNumberOfCircles;
			if(suggestedSize<DEFAULT_SIZE&&limit>DEFAULT_SIZE) suggestedSize= DEFAULT_SIZE;
			if(limit>suggestedSize) return new Dimension(suggestedSize, suggestedSize);
			else if(limit>suggestedSize/2) return screenSize;
			else circleSize= (int)((double)circleSize/1.05);
		}
	}

	
	public int getCorrect() 
	{
		return correct;
	}

	public int getIncorrect() 
	{
		return incorrect;
	}

	public int getCorrectOnFirstTry() 
	{
		return correctOnFirstTry;
	}

	public int getIncorrectOnFirstTry() 
	{
		return incorrectOnFirstTry;
	}

	public long getElapsedTime() 
	{
		return elapsedTime;
	}

	public DynamicLongArray getSessionTimes() 
	{
		return sessionTimes;
	}

	public int getMaxNumberOfCircles() 
	{
		return maxNumberOfCircles;
	}

	public int getMinNumberOfCircles() 
	{
		return minNumberOfCircles;
	}

	public int[] getCorrectAtNumber() 
	{
		return correctAtNumber;
	}

	public int[] getIncorrectAtNumber() 
	{
		return incorrectAtNumber;
	}
	
	public long[] getAverageTimeAtNumber()
	{
		return averageTimeAtNumber;
	}
	
	public int getTimesPlayed()
	{
		return timesPlayed;
	}
	/**
	 * draws the circles and the results if applicable 
	 */
	@Override
	public void paintComponent(Graphics g)
	{
		super.paintComponent(g);
		g.setFont(font);
		if(begun==false)
		{
			g.drawString("Subitizer" ,
				     this.getWidth()/2-DEFAULT_CIRCLE_SIZE, this.getHeight()/2-GAP);
		}
		if(displayResults)
		{
			double percentageCorrect= (double)correct/(double)(correct+incorrect);
			g.drawString("Precentage Correct: "+
						 percentageFormat.format(percentageCorrect),
					     this.getWidth()/2-DEFAULT_CIRCLE_SIZE, this.getHeight()/2);
			double percentageOnFirst= (double)correctOnFirstTry/
									  (double)(correctOnFirstTry+incorrectOnFirstTry); 
			g.drawString("Precentage Correct On First Try: "+
						 percentageFormat.format(percentageOnFirst),
						 this.getWidth()/2-DEFAULT_CIRCLE_SIZE, this.getHeight()/2+GAP);
			double time= (double)elapsedTime/1000.0;
			g.drawString("Elapsed Time: "+timeFormat.format(time),
						 this.getWidth()/2-DEFAULT_CIRCLE_SIZE, this.getHeight()/2+GAP*2);
			double average= (double)sessionTimes.average()/1000.0;
			g.drawString("Average Time This Session: "+timeFormat.format(average),
					 this.getWidth()/2-DEFAULT_CIRCLE_SIZE, this.getHeight()/2+GAP*3);
		}
		if(circles!=null)
		{
			for(int k=0; k<circles.length; k++)
			{
				circles[k].drawCircle(g);
			}
		}
	}
	
	/**
	 * Checks key presses against the number of circles
	 */
	@Override
	public void keyPressed(KeyEvent e) 
	{
		int key= -1;
		if(begun)
		{
			switch(e.getKeyCode())
			{
				case KeyEvent.VK_NUMPAD0:
				case KeyEvent.VK_0:
				{
					key= 0;
					break;
				}
				case KeyEvent.VK_NUMPAD1:
				case KeyEvent.VK_1:
				{
					key= 1;
					break;
				}
				case KeyEvent.VK_NUMPAD2:
				case KeyEvent.VK_2:
				{
					key= 2;
					break;
				}
				case KeyEvent.VK_NUMPAD3:
				case KeyEvent.VK_3:
				{
					key= 3;
					break;
				}
				case KeyEvent.VK_NUMPAD4:
				case KeyEvent.VK_4:
				{
					key= 4;
					break;
				}
				case KeyEvent.VK_NUMPAD5:
				case KeyEvent.VK_5:
				{
					key= 5;
					break;
				}
				case KeyEvent.VK_NUMPAD6:
				case KeyEvent.VK_6:
				{
					key= 6;
					break;
				}
				case KeyEvent.VK_NUMPAD7:
				case KeyEvent.VK_7:
				{
					key= 7;
					break;
				}
				case KeyEvent.VK_NUMPAD8:
				case KeyEvent.VK_8:
				{
					key= 8;
					break;
				}
				case KeyEvent.VK_NUMPAD9:
				case KeyEvent.VK_9:
				{
					key= 9;
					break;
				}
				default: 
				{
					key= -1;
					break;
				}
			}
			
			if(numberOfCircles>=10&&waitingOnSecondKey==false)
			{
				int firstNumber= numberOfCircles/10;
				if(key==firstNumber)
				{
					firstKey= key;
					waitingOnSecondKey= true;
					return;
				}
			}
			else if(waitingOnSecondKey&&key>=0)
			{
				key+= firstKey*10; // second key is for the 10's place
				waitingOnSecondKey= false;
			}
			
			if(key==numberOfCircles&&key<=maxNumberOfCircles&&key>=minNumberOfCircles)
			{
				questionElaspedTime= System.currentTimeMillis()-questionStartTime; 
				if(onFirstTry) correctOnFirstTry++;
				begun= false;
				onFirstTry= true;
				correct++;
				int index= numberOfCircles-1;
				correctAtNumber[index]++;
				// avg= (oldsum+newvalue)/numberofvalues
				// oldsum= oldavg*(numberofvalues-1)
				averageTimeAtNumber[index]= (((long)correctAtNumber[index]-1L)
											  *averageTimeAtNumber[index]
				                             +questionElaspedTime)
				                             /((long)correctAtNumber[index]);
				questionCount++;
				if(questionCount==maxQuestionCount)
				{
					elapsedTime= System.currentTimeMillis()-startTime;
					sessionTimes.add(elapsedTime);
					timesPlayed++;
					endGame();
					return;
				}
				randomizeCircles();
				repaint();
				questionStartTime= System.currentTimeMillis();
			}
			else if(key!=-1&&key<=maxNumberOfCircles&&key>=minNumberOfCircles)
			{
				Toolkit.getDefaultToolkit().beep();
				incorrect++;
				incorrectAtNumber[numberOfCircles-minNumberOfCircles]++;
				if(onFirstTry) incorrectOnFirstTry++;
				onFirstTry= false;
			}
		}
		
	}

	@Override
	public void keyReleased(KeyEvent arg0) 
	{
		
	}

	@Override
	public void keyTyped(KeyEvent arg0) 
	{
		
	}
	
	/**
	 * Creates a random number of circles between 
	 * maxNumberOfCircles and minNumberOfCircles 
	 * and places the circles at random positions. 
	 */
	private void randomizeCircles()
	{
		int upperBound= maxNumberOfCircles-minNumberOfCircles;
		int index= (int)(Math.random()*(upperBound+1))+minNumberOfCircles;
		circles= new Circle[index];
		numberOfCircles= index;
		begun= true;
		boolean overlaping;
		for(int k=0; k<circles.length; k++)
		{
			int j= 0;
			while(j<2000) 
			{
				int x_pos= (int)(Math.random()*(this.getWidth()-circleSize));
				int y_pos= (int)(Math.random()*(this.getHeight()-circleSize));
				circles[k]= new Circle(color, x_pos, y_pos, circleSize);
				// check for overlap
				overlaping= false;
				for(int i=0; i<k; i++)
				{
					if(circles[i].isOverlapping(circles[k]))
					{
						overlaping= true;
						break;
					}
				}
				if(overlaping==false) break;
				j++;
			}
		}
	}
	
	/**
	 * Calls User.addGame(this) on currentUser and sets the game up to be 
	 * replayed. Does not change statistic fields 
	 */
	private void endGame()
	{
		circles= null;
		if(Global.currentUser!=null) Global.currentUser.addGame(this);
		start.setText("Play Again");
		start.setMnemonic(KeyEvent.VK_P);
		start.setVisible(true);
		advanced.setVisible(true);
		backToSettings.setVisible(true);
		begun= false;
		displayResults= true;
		repaint();
	}
	
	/**
	 * resets the statistic fields.
	 */
	private void reset()
	{
		onFirstTry= true;
		correct= 0;
		incorrect= 0;
		correctOnFirstTry= 0;
		incorrectOnFirstTry= 0;
		questionCount= 0;
		startTime= System.currentTimeMillis();
		questionStartTime= System.currentTimeMillis(); 
		repaint();
	}
	
}

	